// let isSMD: boolean = true // 定义一个布尔类型变量
// let age: number = 18 // 定义一个数字类型变量
// let myName: string = 'smd' // 定义字符串
// let arr1: number[] = [1, 2, 3] // 定义一个数字类型数组
// let arr2: Array<number> = [1, 2, 3] // 等价于上边

// // 元组类型 tuple  数量和类型已知的数组 -- 元组
// let xj: [string, number] = ['消金', 10]

// // 枚举类型 enum  定义一个变量，列举他可能出现的值
// enum Size { // 普通枚举
//   BIG,
//   SMALL
// }

// console.log(Size.BIG, Size[0]) // 0 BIG
// console.log(Size.SMALL, Size[1]) // 1 SMALL

// const enum Colors { // 常量枚举
//   RED,
//   YELLOW,
//   BLUE
// }
// const myColor = [Colors.RED, Colors.BLUE, Colors.YELLOW] // 编译后的结果为 var myColor = [0 /* RED */, 2 /* BLUE */, 1 /* YELLOW */];

// // 任意类型 any
// let root: any = document.getElementById('#root')
// // 如果一个变量定义成any类型 则它和js的使用时一样的
// root.style.color = 'red'

// // 如果 strictNullChecks的值为true 则不能这么用  因为null没有.style属性
// let element: (HTMLElement | null) = document.getElementById('#element')
// element!.style.color = 'blue'

// // null undefined 是其他类型的子类型(strictNullChecks不是true的情况下)
// let x: number = 1
// // x = undefined
// // x = null

// let y: number | null | undefined // 或操作符
// y = 1
// y = undefined
// y = null

// // 不会出现的值 never
// // 1.作为不会返回的函数的返回值类型
// function error(msg: string): never { // 返回值为never   不能里边什么都没有  如果什么都没有  他的返回值是undefined, undefined与never是不同的
//   throw Error('出错了')  // 出错了 下边的代码不会执行 也不会有返回值
//   console.log(msg)
// }
// error('123')

// function loop(msg: string): never { // while死循环  也不会有返回值
//   while (true) { }
//   console.log(msg)
// }

// function fn(x: number | string) {
//   if (typeof x === 'number') {
//     console.log(x.toFixed(0))
//   } else if (typeof x === 'string') {
//     console.log(x.trim())
//   } else {
//     console.log(x) // 这个x就是never类型的
//   }
// }

// // void 代表没有任何类型
// function voidFn(): void {
//   // return undefined // 可以返回undefined
//   // return null // 当strictNullChecks 为true时不能返回null   false时可以返回null
//   // return 123 // 其他的返回都不行
// }

// Symbol 唯一不变的类型
let s1 = Symbol('key')
let s2 = Symbol('key') // “Symbol” 仅指类型，但在此处用作值。是否需要更改目标库? 请尝试将 “lib” 编译器选项更改为 es2015 或更高版本。
// 但是tsconfig设置了 lib之后  上边的console就不能用了  需要在lib中加上 “dom”
console.log(s1 === s2) // false

// BigInt
// let max = Number.MAX_SAFE_INTEGER  // 2**53-1 2的53次方-1  这是js中表示的最大的值
// console.log(max + 1 === max + 2) // true
let max = BigInt(Number.MAX_SAFE_INTEGER)
// 错误写法 console.log(max+1 === max+2) // 运算符“+”不能应用于类型“bigint”和“1”
// 正确写法 console.log(max + BigInt(1) === max + BigInt(2)) // false
// 简便写法 console.log(max + 1n === max + 2n) // 需要将tsconfig中的 target 改为 "ESNext"  数字后加个n表示大整形

let num: number
let big: bigint
// 错误写法 num = big  number与bigint类型的不能互转


// 类型推导
let uname // 此时uname 会被ts推导为any类型
let uname2 = 'str' // 声明变量并赋值 被类型推导为赋值的基本类型  等于uname2:string = 'str' 这种写法
// 错误 uname2 = 2  被类型推导后  不能再给他赋值为其他类型的变量

// 包装对象  wrapper object    
// 原始类型-对象类型
// 原始类型
let name2 = 'smd'
console.log(name2.toLocaleUpperCase()); // name2被推导为了字符串 但是字符串本身没有方法 在他的内部会进行自动包装对象：
console.log(new String(name2).toLocaleUpperCase());

// 联合类型
// 声明一个联合类型的变量name3 当它不赋值的时候，只能调用string和number共有的方法
let name3: string | number
console.log(name3!.toLocaleString(), name3!.toString(), name3!.valueOf());
// 但是当他赋值为具体类型之后，就可以调用当前类型下的方法了
name3 = 1
console.log(name3.valueOf(), name3.toString(), name3.toPrecision(), name3.toLocaleString(), name3.toFixed(), name3.toExponential());
name3 = '1'
console.log(name3.sup(), name3.toLocaleLowerCase(), name3.toLocaleUpperCase(), name3.toLowerCase(), name3.toString(), name3.toUpperCase(), name3.trim(), '......');

// 类型断言
let name4: number | string
// 错误写法 console.log(name4!.toFixed(2)); 直接掉length属性是会报错的 因为string上没有length属性
console.log((name4! as number).toFixed(2)); // 将name4类型断言为name4
console.log(name4! as any as number); // 双重断言 先断言成any  再断言成number

// 字面量类型与类型字面量
// 字面量类型
const up: 'Up' = 'Up'
const down: 'Down' = 'Down'
const left: 'Left' = 'Left'
const right: 'Right' = 'Right'

type Direction = 'Up' | 'Down' | 'Left' | 'Right'
function move(direction: Direction) {

}
move('Down') // 入参中只有 'Up' | 'Down' | 'Left' | 'Right' 这四个选项
// 类型字面量
type Person = {
  name: string,
  age: number
}

let p1: Person = {
  name: 'smd',
  age: 18 // 缺一不可
}

// 字面量联合类型 和 联合类型
type T1 = '1' | '2' | '3'
type T2 = number | string | boolean
let t1: T1 = '1' // 字面量联合类型规定只能是其中的某一个
let t2: T2 = 1 // 值随意  只要类型满足其中一个就行

// 函数  可以指定函数的参数类型和返回值类型
function hello(name: string): void {
  console.log(`hello${name}`)
}
hello('smd')

// 函数表达式
type getName = (firstName: string, lastName: string) => string
let myName: getName = (firstName: string, lastName: string): string => {
  return firstName + lastName
}
// age 函数的可选参数
function print1(name: string, age?: number): void {
  console.log(name, age)
}
print1('smd', 18)

// methods 函数的可选参数
function ajax(url: string, methods: string = 'GET') {
  console.log(url, methods)
}
ajax('/')

// numbers 剩余参数
function sum(...numbers: number[]) {
  return numbers.reduce((x: number, y: number) => {
    return x + y
  }, 0)
}
console.log(sum(1, 2, 3))

// 函数的重载
let obj: any = {}

// function attr(val: any) {
//   if (typeof val === 'string') {
//     obj.name = val
//   } else if (typeof val === 'number') {
//     obj.age = val
//   }
// }
// 可以 attr('smd')
// 可以 attr(18)
// 可以 attr(true) // 因为val的入参为any类型

// 但是此时不想允许传除number和string之外的类型
function attr(val: string): void
function attr(val: number): void
function attr(val: any) {
  if (typeof val === 'string') {
    obj.name = val
  } else if (typeof val === 'number') {
    obj.age = val
  }
}
// 可以 attr('smd')
// 可以 attr(18)
// 不可以 attr(true) // 因为attr重载类型只规定了string和number